home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / pyshared / cupshelpers / ppds.py < prev    next >
Encoding:
Python Source  |  2010-06-24  |  38.5 KB  |  1,095 lines

  1. #!/usr/bin/env python
  2.  
  3. ## system-config-printer
  4.  
  5. ## Copyright (C) 2006, 2007, 2008, 2009, 2010 Red Hat, Inc.
  6. ## Copyright (C) 2006 Florian Festi <ffesti@redhat.com>
  7. ## Copyright (C) 2006, 2007, 2008, 2009 Tim Waugh <twaugh@redhat.com>
  8.  
  9. ## This program is free software; you can redistribute it and/or modify
  10. ## it under the terms of the GNU General Public License as published by
  11. ## the Free Software Foundation; either version 2 of the License, or
  12. ## (at your option) any later version.
  13.  
  14. ## This program is distributed in the hope that it will be useful,
  15. ## but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. ## GNU General Public License for more details.
  18.  
  19. ## You should have received a copy of the GNU General Public License
  20. ## along with this program; if not, write to the Free Software
  21. ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  22.  
  23. import cups
  24. from .cupshelpers import parseDeviceID
  25. import string
  26. import locale
  27. import os.path
  28. import re
  29. from . import _debugprint, set_debugprint_fn
  30.  
  31. __all__ = ['ppdMakeModelSplit',
  32.            'PPDs']
  33.  
  34. def ppdMakeModelSplit (ppd_make_and_model):
  35.     """
  36.     Split a ppd-make-and-model string into a canonical make and model pair.
  37.  
  38.     @type ppd_make_and_model: string
  39.     @param ppd_make_and_model: IPP ppd-make-and-model attribute
  40.     @return: a string pair representing the make and the model
  41.     """
  42.  
  43.     # If the string starts with a known model name (like "LaserJet") assume
  44.     # that the manufacturer name is missing and add the manufacturer name
  45.     # corresponding to the model name
  46.     ppd_make_and_model.strip ()
  47.     l = ppd_make_and_model.lower ()
  48.     if (l.startswith ("deskjet") or
  49.         l.startswith ("dj ") or l == "dj" or
  50.         (l.startswith ("dj") and len (l) > 2 and l[2].isdigit ()) or
  51.         l.startswith ("laserjet") or
  52.         l.startswith ("lj") or
  53.         l.startswith ("color laserjet") or
  54.         l.startswith ("color lj") or
  55.         l.startswith ("designjet") or
  56.         l.startswith ("officejet") or
  57.         l.startswith ("oj") or
  58.         l.startswith ("photosmart") or
  59.         l.startswith ("ps ") or
  60.         l.startswith ("psc") or
  61.         l.startswith ("edgeline")):
  62.         make = "HP"
  63.         model = ppd_make_and_model
  64.     elif (l.startswith ("stylus") or
  65.           l.startswith ("aculaser")):
  66.         make = "Epson"
  67.         model = ppd_make_and_model
  68.     elif (l.startswith ("stylewriter") or
  69.           l.startswith ("imagewriter") or
  70.           l.startswith ("deskwriter") or
  71.           l.startswith ("laserwriter")):
  72.         make = "Apple"
  73.         model = ppd_make_and_model
  74.     elif (l.startswith ("pixus") or
  75.           l.startswith ("pixma") or
  76.           l.startswith ("selphy") or
  77.           l.startswith ("imagerunner") or
  78.           l.startswith ("bj") or
  79.           l.startswith ("lbp")):
  80.         make = "Canon"
  81.         model = ppd_make_and_model
  82.     elif (l.startswith ("hl") or
  83.           l.startswith ("dcp") or
  84.           l.startswith ("mfc")):
  85.         make = "Brother"
  86.         model = ppd_make_and_model
  87.     elif (l.startswith ("docuprint") or
  88.           l.startswith ("docupage") or
  89.           l.startswith ("phaser") or
  90.           l.startswith ("workcentre") or
  91.           l.startswith ("homecentre")):
  92.         make = "Xerox"
  93.         model = ppd_make_and_model
  94.     elif (l.startswith ("optra") or
  95.           l.startswith ("jetprinter") or
  96.           l.startswith ("color jetprinter")):
  97.         make = "Lexmark"
  98.         model = ppd_make_and_model
  99.     elif (l.startswith ("magicolor") or
  100.           l.startswith ("pageworks") or
  101.           l.startswith ("pagepro")):
  102.         make = "KONICA MINOLTA"
  103.         model = ppd_make_and_model
  104.     elif (l.startswith ("fs-") or
  105.           l.startswith ("km-") or
  106.           l.startswith ("taskalfa")):
  107.         make = "Kyocera Mita"
  108.         model = ppd_make_and_model
  109.     elif l.startswith ("aficio"):
  110.         make = "Ricoh"
  111.         model = ppd_make_and_model
  112.     elif l.startswith ("varioprint"):
  113.         make = "Oce"
  114.         model = ppd_make_and_model
  115.     elif (l.startswith ("okipage") or
  116.           l.startswith ("microline")):
  117.         make = "Oki"
  118.         model = ppd_make_and_model
  119.  
  120.     # Handle PPDs provided by Turboprint
  121.     elif l.find ("turboprint") != -1:
  122.         t = ppd_make_and_model.find (" TurboPrint")
  123.         if t != -1:
  124.             t2 = ppd_make_and_model.rfind (" TurboPrint")
  125.             if t != t2:
  126.                 ppd_make_and_model = ppd_make_and_model[t + 12:t2]
  127.             else:
  128.                 ppd_make_and_model = ppd_make_and_model[:t]
  129.         try:
  130.             make, model = ppd_make_and_model.split("_", 1)
  131.         except:
  132.             make = ppd_make_and_model
  133.             model = ''
  134.         make = re.sub (r"(?<=[a-z])(?=[0-9])", " ", make)
  135.         make = re.sub (r"(?<=[a-z])(?=[A-Z])", " ", make)
  136.         model = re.sub (r"(?<=[a-z])(?=[0-9])", " ", model)
  137.         model = re.sub (r"(?<=[a-z])(?=[A-Z])", " ", model)
  138.         model = re.sub (r" Jet", "Jet", model)
  139.         model = re.sub (r"Photo Smart", "PhotoSmart", model)
  140.  
  141.     # Special handling for two-word manufacturers
  142.     elif l.startswith ("konica minolta "):
  143.         make = "KONICA MINOLTA"
  144.         model = ppd_make_and_model[15:]
  145.     elif l.startswith ("lexmark international "):
  146.         make = "Lexmark"
  147.         model = ppd_make_and_model[22:]
  148.     elif l.startswith ("kyocera mita "):
  149.         make = "Kyocera Mita"
  150.         model = ppd_make_and_model[13:]
  151.     elif l.startswith ("kyocera "):
  152.         make = "Kyocera Mita"
  153.         model = ppd_make_and_model[8:]
  154.  
  155.     # Finally, take the first word as the name of the manufacturer.
  156.     else:
  157.         try:
  158.             make, model = ppd_make_and_model.split(" ", 1)
  159.         except:
  160.             make = ppd_make_and_model
  161.             model = ''
  162.  
  163.     # Standardised names for manufacturers.
  164.     makel = make.lower ()
  165.     if (makel.startswith ("konica") and
  166.         makel.endswith ("minolta")):
  167.         make = "KONICA MINOLTA"
  168.         makel = "konica minolta"
  169.     elif (makel.startswith ("hewlett") and
  170.           makel.endswith ("packard")):
  171.         make = "HP"
  172.         makel = "hp"
  173.  
  174.     # HP PPDs give NickNames like:
  175.     # *NickName: "HP LaserJet 4 Plus v2013.111 Postscript (recommended)"
  176.     # Find the version number.
  177.     modell = model.lower ()
  178.     v = modell.find (" v")
  179.     if v != -1 and (model[v + 2].isdigit () or
  180.                     (model[v + 2] == '.' and
  181.                      model[v + 3].isdigit ())):
  182.         # Truncate at that point.
  183.         model = model[:v]
  184.         modell = modell[:v]
  185.  
  186.     for suffix in [" hpijs",
  187.                    " foomatic/",
  188.                    " - ",
  189.                    " w/",
  190.                    " (",
  191.                    " postscript",
  192.                    " ps",
  193.                    " ps1",
  194.                    " ps2",
  195.                    " ps3",
  196.                    " pxl",
  197.                    " series",
  198.                    " zjs",              # hpcups
  199.                    " zxs",              # hpcups
  200.                    " pcl3",             # hpcups
  201.                    "_bt",
  202.                    " pcl",              # Canon CQue
  203.                    " ufr ii",           # Canon UFR II
  204.                    ","]:
  205.         s = modell.find (suffix)
  206.         if s != -1:
  207.             model = model[:s]
  208.             modell = modell[:s]
  209.  
  210.     if makel == "hp":
  211.         modelnames = {"dj": "DeskJet",
  212.                       "lj": "LaserJet",
  213.                       "oj": "OfficeJet",
  214.                       "color lj": "Color LaserJet",
  215.                       "ps ": "PhotoSmart",
  216.                       "hp ": ""}
  217.         for (name, fullname) in modelnames.iteritems ():
  218.             if modell.startswith (name):
  219.                 model = fullname + model[len (name):]
  220.                 modell = model.lower ()
  221.  
  222.     for mfr in [ "Apple", "Canon", "Epson", "Lexmark", "Oki" ]:
  223.         if makel == mfr.lower ():
  224.             make = mfr
  225.  
  226.     model = model.strip ()
  227.     return (make, model)
  228.  
  229. # Some drivers are just generally better than others.
  230. # Here is the preference list:
  231. DRIVER_TYPE_DOWNLOADED_NOW = 5
  232. DRIVER_TYPE_FOOMATIC_RECOMMENDED_NON_POSTSCRIPT = 8
  233. DRIVER_TYPE_VENDOR = 10
  234. DRIVER_TYPE_FOOMATIC_RECOMMENDED_POSTSCRIPT = 15
  235. DRIVER_TYPE_FOOMATIC_HPIJS_ON_HP = 17
  236. DRIVER_TYPE_GUTENPRINT_NATIVE_SIMPLIFIED = 20
  237. DRIVER_TYPE_GUTENPRINT_NATIVE = 25
  238. DRIVER_TYPE_SPLIX = 27
  239. DRIVER_TYPE_FOOMATIC_PS = 30
  240. DRIVER_TYPE_FOOMATIC_HPIJS = 40
  241. DRIVER_TYPE_HPCUPS = 45
  242. DRIVER_TYPE_FOOMATIC_GUTENPRINT_SIMPLIFIED = 50
  243. DRIVER_TYPE_FOOMATIC_GUTENPRINT = 60
  244. DRIVER_TYPE_FOOMATIC = 70
  245. DRIVER_TYPE_CUPS = 80
  246. DRIVER_TYPE_FOOMATIC_GENERIC = 90
  247. DRIVER_TYPE_3RD_PARTY_NONFREE = 95
  248. DRIVER_DOES_NOT_WORK = 999
  249. def _getDriverType (ppdname, ppds=None):
  250.     """Decides which of the above types ppdname is."""
  251.     if ppdname.find ("turboprint") != -1:
  252.         return DRIVER_TYPE_3RD_PARTY_NONFREE
  253.     if ppdname.find ("splix")!= -1:
  254.         return DRIVER_TYPE_SPLIX
  255.     if ppdname.find ("hpcups") != -1:
  256.         info = ppds.getInfoFromPPDName (ppdname)
  257.         make_model = info.get ('ppd-make-and-model', '')
  258.         if make_model.find ("plugin") != -1:
  259.             return DRIVER_TYPE_3RD_PARTY_NONFREE
  260.         else:
  261.             return DRIVER_TYPE_HPCUPS
  262.     if (ppdname.find (":") == -1 and
  263.         ppdname.find ("/cups-included/") != -1):
  264.         return DRIVER_TYPE_CUPS
  265.     if ppdname.startswith ("foomatic:"):
  266.         # Foomatic (generated) -- but which driver?
  267.         if ppdname.find ("Generic")!= -1:
  268.             return DRIVER_TYPE_FOOMATIC_GENERIC
  269.         if ppds != None:
  270.             info = ppds.getInfoFromPPDName (ppdname)
  271.             device_id = info.get ('ppd-device-id', '')
  272.             id_dict = parseDeviceID (device_id)
  273.             drv = id_dict.get ('DRV', '')
  274.             drvfields = dict()
  275.             for field in drv.split (','):
  276.                 if len (field) == 0:
  277.                     continue
  278.                 key = field[0]
  279.                 drvfields[key] = field[1:]
  280.  
  281.             ppd_make_and_model = info.get ('ppd-make-and-model', '')
  282.             if (drvfields.get ('R', '0') == '1' or
  283.                 ppd_make_and_model.find ("(recommended)") != -1):
  284.                 if ppd_make_and_model.find ("Postscript") != -1:
  285.                     return DRIVER_TYPE_FOOMATIC_RECOMMENDED_POSTSCRIPT
  286.                 else:
  287.                     return DRIVER_TYPE_FOOMATIC_RECOMMENDED_NON_POSTSCRIPT
  288.         if ppdname.find ("-Postscript")!= -1:
  289.             return DRIVER_TYPE_FOOMATIC_PS
  290.         if ppdname.find ("-hpijs") != -1:
  291.             if ppdname.find ("hpijs-rss") == -1:
  292.                 return DRIVER_TYPE_FOOMATIC_HPIJS
  293.         if ppdname.find ("-gutenprint") != -1:
  294.             if ppdname.find ("-simplified")!= -1:
  295.                 return DRIVER_TYPE_FOOMATIC_GUTENPRINT_SIMPLIFIED
  296.             return DRIVER_TYPE_FOOMATIC_GUTENPRINT
  297.         return DRIVER_TYPE_FOOMATIC
  298.     if ppdname.find ("gutenprint") != -1:
  299.         if (ppdname.find ("/simple") != -1 or
  300.             ppdname.find (".sim-") != -1):
  301.             return DRIVER_TYPE_GUTENPRINT_NATIVE_SIMPLIFIED
  302.         else:
  303.             return DRIVER_TYPE_GUTENPRINT_NATIVE
  304.     if ppdname.find ("-hpijs") != -1:
  305.         if ppdname.find ("hpijs-rss") == -1:
  306.             return DRIVER_TYPE_FOOMATIC_HPIJS
  307.     # Anything else should be a vendor's PPD.
  308.     return DRIVER_TYPE_VENDOR # vendor's own
  309.  
  310.  
  311. class PPDs:
  312.     """
  313.     This class is for handling the list of PPDs returned by CUPS.  It
  314.     indexes by PPD name and device ID, filters by natural language so
  315.     that foreign-language PPDs are not included, and sorts by driver
  316.     type.  If an exactly-matching PPD is not available, it can
  317.     substitute with a PPD for a similar model or for a generic driver.
  318.     """
  319.  
  320.     # Status of match.
  321.     STATUS_SUCCESS = 0
  322.     STATUS_MODEL_MISMATCH = 1
  323.     STATUS_GENERIC_DRIVER = 2
  324.     STATUS_NO_DRIVER = 3
  325.  
  326.     def __init__ (self, ppds, language=None):
  327.         """
  328.         @type ppds: dict
  329.         @param ppds: dict of PPDs as returned by cups.Connection.getPPDs()
  330.  
  331.         @type language: string
  332.     @param language: language name, as given by the first element
  333.         of the pair returned by locale.getlocale()
  334.         """
  335.         self.ppds = ppds.copy ()
  336.         self.makes = None
  337.         self.ids = None
  338.  
  339.         if (language == None or
  340.             language == "C" or
  341.             language == "POSIX"):
  342.             language = "en_US"
  343.  
  344.         u = language.find ("_")
  345.         if u != -1:
  346.             short_language = language[:u]
  347.         else:
  348.             short_language = language
  349.  
  350.         to_remove = []
  351.         for ppdname, ppddict in self.ppds.iteritems ():
  352.             try:
  353.                 natural_language = ppddict['ppd-natural-language']
  354.             except KeyError:
  355.                 continue
  356.  
  357.             if natural_language == "en":
  358.                 # Some manufacturer's PPDs are only available in this
  359.                 # language, so always let them though.
  360.                 continue
  361.  
  362.             if natural_language == language:
  363.                 continue
  364.  
  365.             if natural_language == short_language:
  366.                 continue
  367.  
  368.             to_remove.append (ppdname)
  369.  
  370.         for ppdname in to_remove:
  371.             del self.ppds[ppdname]
  372.  
  373.         # CUPS sets the 'raw' model's ppd-make-and-model to 'Raw Queue'
  374.         # which unfortunately then appears as manufacturer Raw and
  375.         # model Queue.  Use 'Generic' for this model.
  376.         if self.ppds.has_key ('raw'):
  377.             makemodel = self.ppds['raw']['ppd-make-and-model']
  378.             if not makemodel.startswith ("Generic "):
  379.                 self.ppds['raw']['ppd-make-and-model'] = "Generic " + makemodel
  380.  
  381.     def getMakes (self):
  382.         """
  383.     @returns: a list of strings representing makes, sorted according
  384.         to the current locale
  385.     """
  386.         self._init_makes ()
  387.         makes_list = self.makes.keys ()
  388.         makes_list.sort (locale.strcoll)
  389.         try:
  390.             # "Generic" should be listed first.
  391.             makes_list.remove ("Generic")
  392.             makes_list.insert (0, "Generic")
  393.         except ValueError:
  394.             pass
  395.         return makes_list
  396.  
  397.     def getModels (self, make):
  398.         """
  399.     @returns: a list of strings representing models, sorted using
  400.     cups.modelSort()
  401.     """
  402.         self._init_makes ()
  403.         try:
  404.             models_list = self.makes[make].keys ()
  405.         except KeyError:
  406.             return []
  407.         models_list.sort (key=lambda x: x.lower(), cmp=cups.modelSort)
  408.         return models_list
  409.  
  410.     def getInfoFromModel (self, make, model):
  411.         """
  412.     Obtain a list of PPDs that are suitable for use with a
  413.         particular printer model, given its make and model name.
  414.  
  415.     @returns: a dict, indexed by ppd-name, of dicts representing
  416.         PPDs (as given by cups.Connection.getPPDs)
  417.     """
  418.         self._init_makes ()
  419.         try:
  420.             return self.makes[make][model]
  421.         except KeyError:
  422.             return {}
  423.  
  424.     def getInfoFromPPDName (self, ppdname):
  425.         """
  426.     @returns: a dict representing a PPD, as given by
  427.     cups.Connection.getPPDs
  428.     """
  429.         return self.ppds[ppdname]
  430.  
  431.     def orderPPDNamesByPreference (self, ppdnamelist=[],
  432.                                    downloadedfiles=[]):
  433.         """
  434.  
  435.     Sort a list of PPD names by (hard-coded) preferred driver
  436.     type.
  437.  
  438.     @param ppdnamelist: PPD names
  439.     @type ppdnamelist: string list
  440.     @returns: string list
  441.     """
  442.         if len (ppdnamelist) < 1:
  443.             return ppdnamelist
  444.  
  445.         dict = self.getInfoFromPPDName (ppdnamelist[0])
  446.         make_model = dict['ppd-make-and-model']
  447.         mfg, mdl = ppdMakeModelSplit (make_model)
  448.         def getDriverTypeWithBias (x, mfg):
  449.             t = _getDriverType (x, ppds=self)
  450.             for file in downloadedfiles:
  451.                 (path, slash, filename) = file.rpartition ("/")
  452.                 (xpath, xslash, xfilename) = x.rpartition ("/")
  453.                 if filename == xfilename:
  454.                     return DRIVER_TYPE_DOWNLOADED_NOW
  455.             if mfg == "HP" or mfg == "Apollo":
  456.                 if t == DRIVER_TYPE_FOOMATIC_HPIJS:
  457.                     # Prefer HPIJS for HP devices.
  458.                     t = DRIVER_TYPE_FOOMATIC_HPIJS_ON_HP
  459.             return t
  460.  
  461.         def sort_ppdnames (a, b):
  462.             ta = getDriverTypeWithBias (a, mfg)
  463.             tb = getDriverTypeWithBias (b, mfg)
  464.             if ta != tb:
  465.                 if tb < ta:
  466.                     return 1
  467.                 else:
  468.                     return -1
  469.  
  470.             # Prefer C locale localized PPDs to other languages, just
  471.             # because we don't know the user's locale.  This only
  472.             # applies to PPDs provided by gutenprint 5.0; in 5.2 the
  473.             # PPDs are localized properly.
  474.             def is_C_locale (x):
  475.                 try:
  476.                     while x:
  477.                         i = x.find ("C")
  478.                         if i == -1:
  479.                             return False
  480.                         lword = False
  481.                         if i == 0:
  482.                             lword = True
  483.                         elif x[i - 1] not in string.letters:
  484.                             lword = True
  485.  
  486.                         if lword:
  487.                             rword = False
  488.                             if i == (len (x) - 1):
  489.                                 rword = True
  490.                             elif x[i + 1] not in string.letters:
  491.                                 rword = True
  492.                             if rword:
  493.                                 return True
  494.                         
  495.                         x = x[i + 1:]
  496.                 except UnicodeDecodeError:
  497.                     return False
  498.  
  499.             ca = is_C_locale (a)
  500.             cb = is_C_locale (b)
  501.             if ca != cb:
  502.                 # If they compare equal stringwise up to "C", sort.
  503.                 if ca:
  504.                     l = a.find ("C")
  505.                 else:
  506.                     l = b.find ("C")
  507.  
  508.                 if a[:l] == b[:l]:
  509.                     if cb:
  510.                         return 1
  511.                     else:
  512.                         return -1
  513.  
  514.             # String-wise compare.
  515.             if a > b:
  516.                 return 1
  517.             elif a < b:
  518.                 return -1
  519.             return 0
  520.  
  521.         ppdnamelist.sort (sort_ppdnames)
  522.         return ppdnamelist
  523.  
  524.     def getPPDNameFromDeviceID (self, mfg, mdl, description="",
  525.                                 commandsets=[], uri=None,
  526.                                 downloadedfiles=[]):
  527.         """
  528.     Obtain a best-effort PPD match for an IEEE 1284 Device ID.
  529.     The status is one of:
  530.  
  531.       - L{STATUS_SUCCESS}: the match was successful, and an exact
  532.             match was found
  533.  
  534.       - L{STATUS_MODEL_MISMATCH}: a similar match was found, but
  535.             the model name does not exactly match
  536.  
  537.       - L{STATUS_GENERIC_DRIVER}: no match was found, but a
  538.             generic driver is available that can drive this device
  539.             according to its command set list
  540.  
  541.       - L{STATUS_NO_DRIVER}: no match was found at all, and the
  542.             returned PPD name is a last resort
  543.  
  544.     @param mfg: MFG or MANUFACTURER field
  545.     @type mfg: string
  546.     @param mdl: MDL or MODEL field
  547.     @type mdl: string
  548.     @param description: DES or DESCRIPTION field, optional
  549.     @type description: string
  550.     @param commandsets: CMD or COMMANDSET field, optional
  551.     @type commandsets: string
  552.     @param uri: device URI, optional (only needed for debugging)
  553.     @type uri: string
  554.     @returns: an integer,string pair of (status,ppd-name)
  555.     """
  556.         _debugprint ("\n%s %s" % (mfg, mdl))
  557.         self._init_ids ()
  558.  
  559.         # Start with an empty result list and build it up using
  560.         # several search methods, in increasing order of fuzziness.
  561.         ppdnamelist = []
  562.  
  563.         # First, try looking up the device using the manufacturer and
  564.         # model fields from the Device ID exactly as they appear (but
  565.         # case-insensitively).
  566.         mfgl = mfg.lower ()
  567.         mdll = mdl.lower ()
  568.  
  569.         id_matched = False
  570.         try:
  571.             ppdnamelist = self.ids[mfgl][mdll]
  572.             status = self.STATUS_SUCCESS
  573.             id_matched = True
  574.         except KeyError:
  575.             pass
  576.  
  577.         # The HP PPDs say "HP" not "Hewlett-Packard", so try that.
  578.         if mfgl == "hewlett-packard":
  579.             try:
  580.                 ppdnamelist += self.ids["hp"][mdll]
  581.                 status = self.STATUS_SUCCESS
  582.                 print ("**** Incorrect IEEE 1284 Device ID: %s" %
  583.                        self.ids["hp"][mdll])
  584.                 print "**** Actual ID is MFG:%s;MDL:%s;" % (mfg, mdl)
  585.                 print "**** Please report a bug against the HPLIP component"
  586.                 id_matched = True
  587.             except KeyError:
  588.                 pass
  589.  
  590.         # Now try looking up the device by ppd-make-and-model.
  591.         _debugprint ("Trying make/model names")
  592.         mdls = None
  593.         self._init_makes ()
  594.         make = None
  595.         if mfgl == "":
  596.             (mfg, mdl) = ppdMakeModelSplit (mdl)
  597.             mfgl = mfg.lower ()
  598.             mdll = mdl.lower ()
  599.  
  600.         mfgrepl = {"hewlett-packard": "hp",
  601.                    "lexmark international": "lexmark",
  602.                    "kyocera": "kyocera mita"}
  603.         if self.lmakes.has_key (mfgl):
  604.             # Found manufacturer.
  605.             make = self.lmakes[mfgl]
  606.         elif mfgrepl.has_key (mfgl):
  607.             rmfg = mfgrepl[mfgl]
  608.             if self.lmakes.has_key (rmfg):
  609.                 mfg = rmfg
  610.                 mfgl = mfg
  611.                 # Found manufacturer (after mapping to canonical name)
  612.                 make = self.lmakes[mfgl]
  613.  
  614.         if make != None:
  615.             mdls = self.makes[make]
  616.             mdlsl = self.lmodels[make.lower ()]
  617.  
  618.             # Remove manufacturer name from model field
  619.             for prefix in [mfgl, 'hewlett-packard', 'hp']:
  620.                 if mdll.startswith (prefix + ' '):
  621.                     mdl = mdl[len (prefix) + 1:]
  622.                     mdll = mdl.lower ()
  623.  
  624.             if self.lmodels[mfgl].has_key (mdll):
  625.                 model = mdlsl[mdll]
  626.                 ppdnamelist += mdls[model].keys ()
  627.                 status = self.STATUS_SUCCESS
  628.             else:
  629.                 # Make use of the model name clean-up in the
  630.                 # ppdMakeModelSplit () function
  631.                 (mfg2, mdl2) = ppdMakeModelSplit (mfg + " " + mdl)
  632.                 mdl2l = mdl2.lower ()
  633.                 if self.lmodels[mfgl].has_key (mdl2l):
  634.                     model = mdlsl[mdl2l]
  635.                     ppdnamelist += mdls[model].keys ()
  636.                     status = self.STATUS_SUCCESS
  637.       
  638.         if not ppdnamelist and mdls:
  639.             (s, ppds) = self._findBestMatchPPDs (mdls, mdl)
  640.             if s != self.STATUS_NO_DRIVER:
  641.                 status = s
  642.                 ppdnamelist = ppds
  643.  
  644.         if not ppdnamelist and commandsets:
  645.             if type (commandsets) != list:
  646.                 commandsets = commandsets.split (',')
  647.  
  648.             generic = self._getPPDNameFromCommandSet (commandsets)
  649.             if generic:
  650.                 status = self.STATUS_GENERIC_DRIVER
  651.                 ppdnamelist = generic
  652.  
  653.         if not ppdnamelist:
  654.             status = self.STATUS_NO_DRIVER
  655.             fallbacks = ["textonly.ppd", "postscript.ppd"]
  656.             found = False
  657.             for fallback in fallbacks:
  658.                 _debugprint ("'%s' fallback" % fallback)
  659.                 fallbackgz = fallback + ".gz"
  660.                 for ppdpath in self.ppds.keys ():
  661.                     if (ppdpath.endswith (fallback) or
  662.                         ppdpath.endswith (fallbackgz)):
  663.                         ppdnamelist = [ppdpath]
  664.                         found = True
  665.                         break
  666.  
  667.                 if found:
  668.                     break
  669.  
  670.                 _debugprint ("Fallback '%s' not available" % fallback)
  671.  
  672.             if not found:
  673.                 _debugprint ("No fallback available; choosing any")
  674.                 ppdnamelist = [self.ppds.keys ()[0]]
  675.  
  676.         if id_matched:
  677.             _debugprint ("Checking DES field")
  678.             inexact = set()
  679.             if description:
  680.                 for ppdname in ppdnamelist:
  681.                     if ppdname.find ("hpijs"):
  682.                         continue
  683.                     ppddict = self.ppds[ppdname]
  684.                     id = ppddict['ppd-device-id']
  685.                     if not id:
  686.                         continue
  687.                     # Fetch description field.
  688.                     id_dict = parseDeviceID (id)
  689.                     if id_dict["DES"] != description:
  690.                         inexact.add (ppdname)
  691.  
  692.             exact = set (ppdnamelist).difference (inexact)
  693.             _debugprint ("discarding: %s" % inexact)
  694.             if len (exact) >= 1:
  695.                 ppdnamelist = list (exact)
  696.  
  697.         # We've got a set of PPDs, any of which will drive the device.
  698.         # Now we have to choose the "best" one.  This is quite tricky
  699.         # to decide, so let's sort them in order of preference and
  700.         # take the first.
  701.         ppdnamelist = self.orderPPDNamesByPreference (ppdnamelist,
  702.                                                       downloadedfiles)
  703.         _debugprint ("Found PPDs: %s" % str (ppdnamelist))
  704.  
  705.         if not id_matched:
  706.             sanitised_uri = re.sub (pattern="//[^@]*@/?", repl="//",
  707.                                     string=str (uri))
  708.             try:
  709.                 cmd = reduce (lambda x, y: x + ","+ y, commandsets)
  710.             except TypeError:
  711.                 cmd = ""
  712.             id = "MFG:%s;MDL:%s;" % (mfg, mdl)
  713.             if cmd:
  714.                 id += "CMD:%s;" % cmd
  715.             if description:
  716.                 id += "DES:%s;" % description
  717.  
  718.             print "No ID match for device %s:" % sanitised_uri
  719.             print id
  720.  
  721.         print "Using %s (status: %d)" % (ppdnamelist[0], status)
  722.         return (status, ppdnamelist[0])
  723.  
  724.     def _findBestMatchPPDs (self, mdls, mdl):
  725.         """
  726.         Find the best-matching PPDs based on the MDL Device ID.
  727.         This function could be made a lot smarter.
  728.         """
  729.  
  730.         _debugprint ("Trying best match")
  731.         mdll = mdl.lower ()
  732.         if mdll.endswith (" series"):
  733.             # Strip " series" from the end of the MDL field.
  734.             mdll = mdll[:-7]
  735.             mdl = mdl[:-7]
  736.         best_mdl = None
  737.         best_matchlen = 0
  738.         mdlnames = mdls.keys ()
  739.  
  740.         # Perform a case-insensitive model sort on the names.
  741.         mdlnamesl = map (lambda x: (x, x.lower()), mdlnames)
  742.         mdlnamesl.append ((mdl, mdll))
  743.         mdlnamesl.sort (lambda x, y: cups.modelSort(x[1], y[1]))
  744.         i = mdlnamesl.index ((mdl, mdll))
  745.         candidates = [mdlnamesl[i - 1]]
  746.         if i + 1 < len (mdlnamesl):
  747.             candidates.append (mdlnamesl[i + 1])
  748.             _debugprint (candidates[0][0] + " <= " + mdl + " <= " +
  749.                         candidates[1][0])
  750.         else:
  751.             _debugprint (candidates[0][0] + " <= " + mdl)
  752.  
  753.         # Look at the models immediately before and after ours in the
  754.         # sorted list, and pick the one with the longest initial match.
  755.         for (candidate, candidatel) in candidates:
  756.             prefix = os.path.commonprefix ([candidatel, mdll])
  757.             if len (prefix) > best_matchlen:
  758.                 best_mdl = mdls[candidate].keys ()
  759.                 best_matchlen = len (prefix)
  760.                 _debugprint ("%s: match length %d" % (candidate, best_matchlen))
  761.  
  762.         # Did we match more than half of the model name?
  763.         if best_mdl and best_matchlen > (len (mdll) / 2):
  764.             ppdnamelist = best_mdl
  765.             if best_matchlen == len (mdll):
  766.                 status = self.STATUS_SUCCESS
  767.             else:
  768.                 status = self.STATUS_MODEL_MISMATCH
  769.         else:
  770.             status = self.STATUS_NO_DRIVER
  771.             ppdnamelist = None
  772.  
  773.             # Last resort.  Find the "most important" word in the MDL
  774.             # field and look for a match based solely on that.  If
  775.             # there are digits, try lowering the number of
  776.             # significant figures.
  777.             mdlnames.sort (cups.modelSort)
  778.             mdlitems = map (lambda x: (x.lower (), mdls[x]), mdlnames)
  779.             modelid = None
  780.             for word in mdll.split (' '):
  781.                 if modelid == None:
  782.                     modelid = word
  783.  
  784.                 have_digits = False
  785.                 for i in range (len (word)):
  786.                     if word[i].isdigit ():
  787.                         have_digits = True
  788.                         break
  789.  
  790.                 if have_digits:
  791.                     modelid = word
  792.                     break
  793.  
  794.             digits = 0
  795.             digits_start = -1
  796.             digits_end = -1
  797.             for i in range (len (modelid)):
  798.                 if modelid[i].isdigit ():
  799.                     if digits_start == -1:
  800.                         digits_start = i
  801.                     digits_end = i
  802.                     digits += 1
  803.                 elif digits_start != -1:
  804.                     break
  805.             digits_end += 1
  806.             modelnumber = 0
  807.             if digits > 0:
  808.                 modelnumber = int (modelid[digits_start:digits_end])
  809.                 modelpattern = (modelid[:digits_start] + "%d" +
  810.                                 modelid[digits_end:])
  811.                 _debugprint ("Searching for model ID '%s', '%s' %% %d" %
  812.                              (modelid, modelpattern, modelnumber))
  813.                 ignore_digits = 0
  814.                 best_mdl = None
  815.                 found = False
  816.                 while ignore_digits < digits:
  817.                     div = pow (10, ignore_digits)
  818.                     modelid = modelpattern % ((modelnumber / div) * div)
  819.                     _debugprint ("Ignoring %d of %d digits, trying %s" %
  820.                                  (ignore_digits, digits, modelid))
  821.  
  822.                     for (name, ppds) in mdlitems:
  823.                         for word in name.split (' '):
  824.                             if word.lower () == modelid:
  825.                                 found = True
  826.                                 break
  827.  
  828.                         if found:
  829.                             best_mdl = ppds.keys ()
  830.                             break
  831.  
  832.                     if found:
  833.                         break
  834.  
  835.                     ignore_digits += 1
  836.                     if digits < 2:
  837.                         break
  838.  
  839.                 if found:
  840.                     ppdnamelist = best_mdl
  841.                     status = self.STATUS_MODEL_MISMATCH
  842.  
  843.         return (status, ppdnamelist)
  844.  
  845.     def _getPPDNameFromCommandSet (self, commandsets=[]):
  846.         """Return ppd-name list or None, given a list of strings representing
  847.         the command sets supported."""
  848.         try:
  849.             self._init_makes ()
  850.             models = self.makes["Generic"]
  851.         except KeyError:
  852.             return None
  853.  
  854.         def get (*candidates):
  855.             for model in candidates:
  856.                 (s, ppds) = self._findBestMatchPPDs (models, model)
  857.                 if s == self.STATUS_SUCCESS:
  858.                     return ppds
  859.             return None
  860.  
  861.         cmdsets = map (lambda x: x.lower (), commandsets)
  862.         if (("postscript" in cmdsets) or ("postscript2" in cmdsets) or
  863.             ("postscript level 2 emulation" in cmdsets)):
  864.             return get ("PostScript Printer")
  865.         elif (("pclxl" in cmdsets) or ("pcl-xl" in cmdsets) or
  866.               ("pcl6" in cmdsets) or ("pcl 6 emulation" in cmdsets)):
  867.             return get ("PCL 6/PCL XL Printer", "PCL Laser Printer")
  868.         elif "pcl5e" in cmdsets:
  869.             return get ("PCL 5e Printer", "PCL Laser Printer")
  870.         elif "pcl5c" in cmdsets:
  871.             return get ("PCL 5c Printer", "PCL Laser Printer")
  872.         elif ("pcl5" in cmdsets) or ("pcl 5 emulation" in cmdsets):
  873.             return get ("PCL 5 Printer", "PCL Laser Printer")
  874.         elif "pcl" in cmdsets:
  875.             return get ("PCL 3 Printer", "PCL Laser Printer")
  876.         elif (("escpl2" in cmdsets) or ("esc/p2" in cmdsets) or
  877.               ("escp2e" in cmdsets)):
  878.             return get ("ESC/P Dot Matrix Printer")
  879.         return None
  880.  
  881.     def _init_makes (self):
  882.         if self.makes:
  883.             return
  884.  
  885.         makes = {}
  886.         lmakes = {}
  887.         lmodels = {}
  888.         for ppdname, ppddict in self.ppds.iteritems ():
  889.             ppd_make_and_model = ppddict['ppd-make-and-model']
  890.             (make, model) = ppdMakeModelSplit (ppd_make_and_model)
  891.             lmake = make.lower ()
  892.             lmodel = model.lower ()
  893.             if not lmakes.has_key (lmake):
  894.                 lmakes[lmake] = make
  895.                 lmodels[lmake] = {}
  896.                 makes[make] = {}
  897.             else:
  898.                 make = lmakes[lmake]
  899.  
  900.             if not lmodels[lmake].has_key (lmodel):
  901.                 lmodels[lmake][lmodel] = model
  902.                 makes[make][model] = {}
  903.             else:
  904.                 model = lmodels[lmake][lmodel]
  905.  
  906.             makes[make][model][ppdname] = ppddict
  907.  
  908.         self.makes = makes
  909.         self.lmakes = lmakes
  910.         self.lmodels = lmodels
  911.  
  912.     def _init_ids (self):
  913.         if self.ids:
  914.             return
  915.  
  916.         ids = {}
  917.         for ppdname, ppddict in self.ppds.iteritems ():
  918.             if not ppddict.has_key ('ppd-device-id'):
  919.                 continue
  920.             id = ppddict['ppd-device-id']
  921.             if not id:
  922.                 continue
  923.  
  924.             id_dict = parseDeviceID (id)
  925.             lmfg = id_dict['MFG'].lower ()
  926.             lmdl = id_dict['MDL'].lower ()
  927.  
  928.             bad = False
  929.             if len (lmfg) == 0:
  930.                 _debugprint ("Missing MFG field for %s" % ppdname)
  931.                 bad = True
  932.             if len (lmdl) == 0:
  933.                 _debugprint ("Missing MDL field for %s" % ppdname)
  934.                 bad = True
  935.             if bad:
  936.                 continue
  937.  
  938.             if not ids.has_key (lmfg):
  939.                 ids[lmfg] = {}
  940.  
  941.             if not ids[lmfg].has_key (lmdl):
  942.                 ids[lmfg][lmdl] = []
  943.  
  944.             ids[lmfg][lmdl].append (ppdname)
  945.  
  946.         self.ids = ids
  947.  
  948. def _show_help():
  949.     print "usage: ppds.py [--deviceid] [--list-models] [--list-ids] [--debug]"
  950.  
  951. def _self_test(argv):
  952.     import sys, getopt
  953.     try:
  954.         opts, args = getopt.gnu_getopt (argv[1:], '',
  955.                                         ['help',
  956.                                          'deviceid',
  957.                                          'list-models',
  958.                                          'list-ids',
  959.                                          'debug'])
  960.     except getopt.GetoptError:
  961.         _show_help()
  962.         sys.exit (1)
  963.  
  964.     stdin_deviceid = False
  965.     list_models = False
  966.     list_ids = False
  967.  
  968.     for opt, optarg in opts:
  969.         if opt == "--help":
  970.             _show_help ()
  971.             sys.exit (0)
  972.         if opt == "--deviceid":
  973.             stdin_deviceid = True
  974.         elif opt == "--list-models":
  975.             list_models = True
  976.         elif opt == "--list-ids":
  977.             list_ids = True
  978.         elif opt == "--debug":
  979.             def _dprint(x):
  980.                 try:
  981.                     print x
  982.                 except:
  983.                     pass
  984.  
  985.             set_debugprint_fn (_dprint)
  986.  
  987.     picklefile="pickled-ppds"
  988.     import pickle
  989.     try:
  990.         f = open (picklefile, "r")
  991.         cupsppds = pickle.load (f)
  992.     except IOError:
  993.         f = open (picklefile, "w")
  994.         c = cups.Connection ()
  995.         cupsppds = c.getPPDs ()
  996.         pickle.dump (cupsppds, f)
  997.  
  998.     ppds = PPDs (cupsppds)
  999.     makes = ppds.getMakes ()
  1000.     models_count = 0
  1001.     for make in makes:
  1002.         models = ppds.getModels (make)
  1003.         models_count += len (models)
  1004.         if list_models:
  1005.             print make
  1006.             for model in models:
  1007.                 print "  " + model
  1008.     print "%d makes, %d models" % (len (makes), models_count)
  1009.     ppds.getPPDNameFromDeviceID ("HP", "PSC 2200 Series")
  1010.     makes = ppds.ids.keys ()
  1011.     models_count = 0
  1012.     for make in makes:
  1013.         models = ppds.ids[make]
  1014.         models_count += len (models)
  1015.         if list_ids:
  1016.             print make
  1017.             for model in models:
  1018.                 print "  %s (%d)" % (model, len (ppds.ids[make][model]))
  1019.                 for driver in ppds.ids[make][model]:
  1020.                     print "    " + driver
  1021.     print "%d ID makes, %d ID models" % (len (makes), models_count)
  1022.  
  1023.     print "\nID matching tests\n"
  1024.  
  1025.     idlist = [
  1026.         # Format is:
  1027.         # (ID string, max status code, expected ppd-make-and-model RE match)
  1028.  
  1029.         # Specific models
  1030.         ("MFG:EPSON;CMD:ESCPL2,BDC,D4,D4PX;MDL:Stylus D78;CLS:PRINTER;"
  1031.          "DES:EPSON Stylus D78;", 1, 'Epson Stylus D68'),
  1032.         ("MFG:Hewlett-Packard;MDL:LaserJet 1200 Series;"
  1033.          "CMD:MLC,PCL,POSTSCRIPT;CLS:PRINTER;", 0, 'HP LaserJet 1200'),
  1034.         ("MFG:Hewlett-Packard;MDL:LaserJet 3390 Series;"
  1035.          "CMD:MLC,PCL,POSTSCRIPT;CLS:PRINTER;", 0, 'HP LaserJet 3390'),
  1036.         ("MFG:Hewlett-Packard;MDL:PSC 2200 Series;CMD:MLC,PCL,PML,DW-PCL,DYN;"
  1037.          "CLS:PRINTER;1284.4DL:4d,4e,1;", 0, "HP PSC 22[01]0"),
  1038.         ("MFG:HEWLETT-PACKARD;MDL:DESKJET 990C;CMD:MLC,PCL,PML;CLS:PRINTER;"
  1039.          "DES:Hewlett-Packard DeskJet 990C;", 0, "HP DeskJet 990C"),
  1040.         ("CLASS:PRINTER;MODEL:HP LaserJet 6MP;MANUFACTURER:Hewlett-Packard;"
  1041.          "DESCRIPTION:Hewlett-Packard LaserJet 6MP Printer;"
  1042.          "COMMAND SET:PJL,MLC,PCLXL,PCL,POSTSCRIPT;", 0, "HP LaserJet 6MP"),
  1043.         # Canon PIXMA iP3000 (from gutenprint)
  1044.         ("MFG:Canon;CMD:BJL,BJRaster3,BSCCe;SOJ:TXT01;MDL:iP3000;CLS:PRINTER;"
  1045.          "DES:Canon iP3000;VER:1.09;STA:10;FSI:03;", 1, "Canon PIXMA iP3000"),
  1046.         ("MFG:HP;MDL:Deskjet 5400 series;CMD:MLC,PCL,PML,DW-PCL,DESKJET,DYN;"
  1047.          "1284.4DL:4d,4e,1;CLS:PRINTER;DES:5440;",
  1048.          1, "HP DeskJet (5440|5550)"), # foomatic-db-hpijs used to say 5440
  1049.         ("MFG:Hewlett-Packard;MDL:HP LaserJet 3390;"
  1050.          "CMD:PJL,MLC,PCL,POSTSCRIPT,PCLXL;",
  1051.          0, "HP LaserJet 3390"),
  1052.  
  1053.         # Generic models
  1054.         ("MFG:New;MDL:Unknown PS Printer;CMD:POSTSCRIPT;",
  1055.          2, "Generic postscript printer"),
  1056.         ("MFG:New;MDL:Unknown PCL6 Printer;CMD:PCLXL;", 2, "Generic PCL 6"),
  1057.         ("MFG:New;MDL:Unknown PCL5e Printer;CMD:PCL5e;", 2, "Generic PCL 5e"),
  1058.         ("MFG:New;MDL:Unknown PCL5c Printer;CMD:PCL5c;", 2, "Generic PCL 5c"),
  1059.         ("MFG:New;MDL:Unknown PCL5 Printer;CMD:PCL5;", 2, "Generic PCL 5"),
  1060.         ("MFG:New;MDL:Unknown PCL3 Printer;CMD:PCL;", 2, "Generic PCL"),
  1061.         ("MFG:New;MDL:Unknown Printer;", 100, None),
  1062.         ]
  1063.  
  1064.     if stdin_deviceid:
  1065.         idlist = [(raw_input ('Device ID: '), 2, '')]
  1066.  
  1067.     all_passed = True
  1068.     for id, max_status_code, modelre in idlist:
  1069.         id_dict = parseDeviceID (id)
  1070.         (status, ppdname) = ppds.getPPDNameFromDeviceID (id_dict["MFG"],
  1071.                                                          id_dict["MDL"],
  1072.                                                          id_dict["DES"],
  1073.                                                          id_dict["CMD"])
  1074.         ppddict = ppds.getInfoFromPPDName (ppdname)
  1075.         if status < max_status_code:
  1076.             success = True
  1077.         else:
  1078.             if status == max_status_code:
  1079.                 match = re.match (modelre, ppddict['ppd-make-and-model'], re.I)
  1080.                 success = match != None
  1081.             else:
  1082.                 success = False
  1083.  
  1084.         if success:
  1085.             result = "PASS"
  1086.         else:
  1087.             result = "*** FAIL ***"
  1088.  
  1089.         print "%s: %s %s (%s)" % (result, id_dict["MFG"], id_dict["MDL"],
  1090.                                   ppddict['ppd-make-and-model'])
  1091.         all_passed = all_passed and success
  1092.  
  1093.     if not all_passed:
  1094.         raise RuntimeError
  1095.